package com.github.bitsapling.sapling.service.impl;

import com.github.bitsapling.sapling.config.SecurityConfig;
import com.github.bitsapling.sapling.enums.LoginType;
import com.github.bitsapling.sapling.exception.MyCustomException;
import com.github.bitsapling.sapling.result.ResponseStatusEnum;
import com.github.bitsapling.sapling.entity.User;
import com.github.bitsapling.sapling.entity.redis.RedisLoginAttempt;
import com.github.bitsapling.sapling.repository.RedisLoginAttemptRepository;
import com.github.bitsapling.sapling.service.AuthenticationService;
import com.github.bitsapling.sapling.service.LoginHistoryService;
import com.github.bitsapling.sapling.service.SettingService;
import com.github.bitsapling.sapling.service.UserService;
import com.github.bitsapling.sapling.utils.IPUtil;
import com.github.bitsapling.sapling.utils.PasswordHash;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service;

import java.util.Optional;


@Service
@Slf4j
public class AuthenticationServiceImpl implements AuthenticationService {
    @Resource
    private HttpServletRequest request;

    @Resource
    private LoginHistoryService loginHistoryService;

    @Resource
    private SettingService settingService;

    @Resource
    private UserService userService;

    @Resource
    private RedisLoginAttemptRepository repository;

    private SecurityConfig getSecurityConfig() {
        return settingService.get(SecurityConfig.getConfigKey(), SecurityConfig.class);
    }

    @Override
    public boolean authenticate(@NotNull User user, @NotNull String password, @Nullable String ipAddress) {
        checkAccountLoginAttempts(ipAddress);
        boolean verify = PasswordHash.verify(password, user.getPassword());
        if (StringUtils.isEmpty(ipAddress)) {
            ipAddress = IPUtil.getRequestIp(request);
        }
        if (verify) {
            cleanUserLoginFail(ipAddress);
            loginHistoryService.create(user, LoginType.ACCOUNT, ipAddress, request.getHeader("User-Agent"));
        } else {
            markUserLoginFail(ipAddress);
        }
        return verify;
    }

    @Override
    public User authenticate(@NotNull String passkey, @Nullable String ipAddress) {
        checkPasskeyLoginAttempts(ipAddress);
        User user = userService.getUserByPasskey(passkey);
        if (StringUtils.isEmpty(ipAddress)) {
            ipAddress = IPUtil.getRequestIp(request);
        }
        if (user != null) {
            cleanUserLoginFail(ipAddress);
            loginHistoryService.create(user, LoginType.PASSKEY, ipAddress, request.getHeader("User-Agent"));
        } else {
            markUserLoginFail(ipAddress);
        }
        return user;
    }

    @Override
    public void cleanUserLoginFail(@Nullable String ip) {
        if(ip == null) return;
        Optional<RedisLoginAttempt> optional = repository.findByIp(ip);
        optional.ifPresent(redisLoginAttempt -> repository.delete(redisLoginAttempt));
    }

    @Override
    public long markUserLoginFail(@Nullable String ip) {
        if(ip == null) return 0;
        Optional<RedisLoginAttempt> optional = repository.findByIp(ip);
        RedisLoginAttempt loginAttempt;
        if (optional.isPresent()) {
            loginAttempt = optional.get();
            loginAttempt.setAttempts(loginAttempt.getAttempts() + 1);
        } else {
            loginAttempt = new RedisLoginAttempt();
            loginAttempt.setIp(ip);
        }
        loginAttempt.setLastAttempt(System.currentTimeMillis());
        loginAttempt = repository.save(loginAttempt);
        return loginAttempt.getAttempts();
    }

    @Override
    public void checkAccountLoginAttempts(@Nullable String ip){
        if(ip == null) return;
        if (getUserFail(ip) > getSecurityConfig().getMaxAuthenticationAttempts()) {
            throw new MyCustomException(ResponseStatusEnum.TOO_MANY_FAILED_AUTHENTICATION_ATTEMPTS);
        }
    }

    @Override
    public void checkPasskeyLoginAttempts(@Nullable String ip) {
        if(ip == null) return;
        if (getUserFail(ip) > getSecurityConfig().getMaxPasskeyAuthenticationAttempts()) {
            throw new MyCustomException(ResponseStatusEnum.TOO_MANY_FAILED_AUTHENTICATION_ATTEMPTS);
        }
    }

    @Override
    public long getUserFail(@Nullable String ip) {
        if (ip == null) return 0;
        Optional<RedisLoginAttempt> optional = repository.findByIp(ip);
        long attempts = optional.map(RedisLoginAttempt::getAttempts).orElse(0L);
        return attempts;
    }
}
